/*
 * Copyright 2002-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.aop.aspectj.annotation;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.AjType;
import org.aspectj.lang.reflect.AjTypeSystem;
import org.aspectj.lang.reflect.PerClauseKind;

import org.springframework.aop.Pointcut;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.TypePatternClassFilter;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.aop.support.ComposablePointcut;

/**
 * Metadata for an AspectJ aspect class, with an additional Spring AOP pointcut
 * for the per clause.
 *
 * <p>Uses AspectJ 5 AJType reflection API, enabling us to work with different
 * AspectJ instantiation models such as "singleton", "pertarget" and "perthis".
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @since 2.0
 * @see org.springframework.aop.aspectj.AspectJExpressionPointcut
 */
/*
1. 总结
AspectMetadata 是用于存储 AspectJ 切面类元数据的类，包含切面名称、切面类以及对应的 AspectJ 反射信息
	它支持不同的 AspectJ 实例化模型，如 "singleton"、"pertarget" 和 "perthis"
	该类实现了 Serializable 接口，能够在序列化后重新解析 AspectJ 的 AjType 信息
2. 内部成员说明
	字段
		aspectName: 切面在 Spring 中定义的名称（即 bean 名称）
		aspectClass: 切面类，用于反序列化时重新解析对应的 AjType
		ajType: AspectJ 反射信息，包含切面的详细元数据
		perClausePointcut: 与切面 per 子句对应的 Spring AOP 切点
	方法
		AspectMetadata(Class<?> aspectClass, String aspectName): 构造函数，创建指定切面类和名称的元数据实例
		findPerClause(Class<?> aspectClass): 从 @Aspect 注解中提取 per 子句内容
		getAjType(): 返回 AspectJ 反射信息
		getAspectClass(): 返回切面类
		getAspectName(): 返回切面名称
		getPerClausePointcut(): 返回与 per 子句对应的 Spring 切点表达式
		isPerThisOrPerTarget(): 判断切面是否定义为 "perthis" 或 "pertarget"
		isPerTypeWithin(): 判断切面是否定义为 "pertypewithin"
		isLazilyInstantiated(): 判断切面是否需要延迟实例化
		readObject(ObjectInputStream): 反序列化时重新解析 AjType 信息
3. 类关系结构
	AspectMetadata 是一个独立的类，不继承其他类或实现其他接口（除了 Serializable）。
相关类和接口区别
	AjType: AspectJ 5 的反射 API 类型，提供对 AspectJ 特定信息的访问
	Pointcut: Spring AOP 的切点接口，用于匹配连接点
	AspectJExpressionPointcut: 基于 AspectJ 表达式的切点实现
	MetadataAwareAspectInstanceFactory: 使用 AspectMetadata 的切面实例工厂接口
4. 使用场景
	Spring AOP 在处理 AspectJ 注解切面时，用于存储和传递切面的元数据信息
	根据切面的不同实例化模型（singleton、pertarget、perthis 等）创建相应的切点表达式
	在切面实例化工厂中提供切面的基本信息和配置
5. 实际代码应用示例
// 定义一个 perthis 切面
@Aspect("perthis(execution(* com.example.service.*.*(..)))")
@Component
public class MyPerThisAspect {
    @Before("execution(* *(..))")
    public void beforeAdvice() {
        System.out.println("Before method execution");
    }
}

// Spring 内部使用 AspectMetadata 处理该切面
public class AspectProcessingExample {
    public void processAspect() {
        // 创建切面元数据
        AspectMetadata metadata = new AspectMetadata(MyPerThisAspect.class, "myPerThisAspect");

        // 获取切面信息
        System.out.println("Aspect Name: " + metadata.getAspectName());
        System.out.println("Aspect Class: " + metadata.getAspectClass().getName());
        System.out.println("Is Lazily Instantiated: " + metadata.isLazilyInstantiated());

        // 获取对应的 per 子句切点
        Pointcut perClausePointcut = metadata.getPerClausePointcut();
    }
}

 */
@SuppressWarnings("serial")
public class AspectMetadata implements Serializable {

	/**
	 * The name of this aspect as defined to Spring (the bean name) -
	 * allows us to determine if two pieces of advice come from the
	 * same aspect and hence their relative precedence.
	 */
	private final String aspectName;

	/**
	 * The aspect class, stored separately for re-resolution of the
	 * corresponding AjType on deserialization.
	 */
	private final Class<?> aspectClass;

	/**
	 * AspectJ reflection information (AspectJ 5 / Java 5 specific).
	 * Re-resolved on deserialization since it isn't serializable itself.
	 */
	private transient AjType<?> ajType;

	/**
	 * Spring AOP pointcut corresponding to the per clause of the
	 * aspect. Will be the Pointcut.TRUE canonical instance in the
	 * case of a singleton, otherwise an AspectJExpressionPointcut.
	 */
	private final Pointcut perClausePointcut;


	/**
	 * Create a new AspectMetadata instance for the given aspect class.
	 * @param aspectClass the aspect class
	 * @param aspectName the name of the aspect
	 */
	public AspectMetadata(Class<?> aspectClass, String aspectName) {
		this.aspectName = aspectName;

		Class<?> currClass = aspectClass;
		AjType<?> ajType = null;
		while (currClass != Object.class) {
			AjType<?> ajTypeToCheck = AjTypeSystem.getAjType(currClass);
			if (ajTypeToCheck.isAspect()) {
				ajType = ajTypeToCheck;
				break;
			}
			currClass = currClass.getSuperclass();
		}
		if (ajType == null) {
			throw new IllegalArgumentException("Class '" + aspectClass.getName() + "' is not an @AspectJ aspect");
		}
		if (ajType.getDeclarePrecedence().length > 0) {
			throw new IllegalArgumentException("DeclarePrecedence not presently supported in Spring AOP");
		}
		this.aspectClass = ajType.getJavaClass();
		this.ajType = ajType;

		switch (this.ajType.getPerClause().getKind()) {
			case SINGLETON:
				this.perClausePointcut = Pointcut.TRUE;
				return;
			case PERTARGET:
			case PERTHIS:
				AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
				ajexp.setLocation(aspectClass.getName());
				ajexp.setExpression(findPerClause(aspectClass));
				ajexp.setPointcutDeclarationScope(aspectClass);
				this.perClausePointcut = ajexp;
				return;
			case PERTYPEWITHIN:
				// Works with a type pattern
				this.perClausePointcut = new ComposablePointcut(new TypePatternClassFilter(findPerClause(aspectClass)));
				return;
			default:
				throw new AopConfigException(
						"PerClause " + ajType.getPerClause().getKind() + " not supported by Spring AOP for " + aspectClass);
		}
	}

	/**
	 * Extract contents from String of form {@code pertarget(contents)}.
	 */
	private String findPerClause(Class<?> aspectClass) {
		String str = aspectClass.getAnnotation(Aspect.class).value();
		int beginIndex = str.indexOf('(') + 1;
		int endIndex = str.length() - 1;
		return str.substring(beginIndex, endIndex);
	}


	/**
	 * Return AspectJ reflection information.
	 */
	public AjType<?> getAjType() {
		return this.ajType;
	}

	/**
	 * Return the aspect class.
	 */
	public Class<?> getAspectClass() {
		return this.aspectClass;
	}

	/**
	 * Return the aspect name.
	 */
	public String getAspectName() {
		return this.aspectName;
	}

	/**
	 * Return a Spring pointcut expression for a singleton aspect.
	 * (e.g. {@code Pointcut.TRUE} if it's a singleton).
	 */
	public Pointcut getPerClausePointcut() {
		return this.perClausePointcut;
	}

	/**
	 * Return whether the aspect is defined as "perthis" or "pertarget".
	 */
	public boolean isPerThisOrPerTarget() {
		PerClauseKind kind = getAjType().getPerClause().getKind();
		return (kind == PerClauseKind.PERTARGET || kind == PerClauseKind.PERTHIS);
	}

	/**
	 * Return whether the aspect is defined as "pertypewithin".
	 */
	public boolean isPerTypeWithin() {
		PerClauseKind kind = getAjType().getPerClause().getKind();
		return (kind == PerClauseKind.PERTYPEWITHIN);
	}

	/**
	 * Return whether the aspect needs to be lazily instantiated.
	 */
	public boolean isLazilyInstantiated() {
		return (isPerThisOrPerTarget() || isPerTypeWithin());
	}


	private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
		inputStream.defaultReadObject();
		this.ajType = AjTypeSystem.getAjType(this.aspectClass);
	}

}
